﻿using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace Devonline.Identity;

/// <summary>
/// 登录现在由统一认证提供
/// 在还没有系统账户的情况下, 在认证完成后, 可以通过手机号码和短信验证码登录系统
/// </summary>
public class RealNameAuthenticationService(
    ILogger<RealNameAuthenticationService> logger,
    IHttpContextAccessor httpContextAccessor,
    IDataWithAttachmentService<RealNameInfo> dataService,
    IDataService<User> userService,
    IFileService fileService,
    IIdCardService idCardService,
    IFaceRecognitionService faceRecognitionService,
    PhoneNumberCaptchaService phoneNumberCaptchaService,
    HttpSetting httpSetting)
{
    private readonly ILogger<RealNameAuthenticationService> _logger = logger;
    private readonly IDataWithAttachmentService<RealNameInfo> _dataService = dataService;
    private readonly IDataService<User> _userService = userService;
    private readonly IFileService _fileService = fileService;
    private readonly IIdCardService _idCardService = idCardService;
    private readonly IFaceRecognitionService _faceRecognitionService = faceRecognitionService;
    private readonly HttpSetting _httpSetting = httpSetting;
    private readonly PhoneNumberCaptchaService _phoneNumberCaptchaService = phoneNumberCaptchaService;
    private readonly HttpContext _httpContext = httpContextAccessor.HttpContext!;

    /// <summary>
    /// 获取用户认证信息
    /// 未创建用户(第三方认证登录)的情况下, 调用此方法无法获取用户及实名认证信息
    /// 使用当前用户编号作为实名认证编号查询实名认证信息, 能查到即为存在实名认证信息
    /// </summary>
    /// <returns></returns>
    public async Task<RealNameInfo?> GetAsync()
    {
        _logger.LogInformation($"{_dataService.UserName} 获取个人实名认证信息!");
        var userId = _dataService.GetUserId();
        if (userId is null)
        {
            _logger.LogInformation("用户未登录, 暂未获取到个人实名认证信息");
            return default;
        }

        return await _dataService.FirstOrDefaultAsync(x => x.Id == userId || x.UserId == userId);
    }

    /// <summary>
    /// 获取手机验证码
    /// 发送短信验证码
    /// 5 分钟内不会发送重复短信,
    /// 5 分钟内未收到可再次发送, 此时请传递 force 参数, 值为 true
    /// </summary>
    public async Task<RealNameViewModel> SendPhoneNumberCaptchaAsync(string phoneNumber)
    {
        var info = await _dataService.FirstOrDefaultAsync(x => x.Id == _dataService.UserId || x.UserId == _dataService.UserId);
        var viewModel = GetRealNameViewModel(info);
        await _phoneNumberCaptchaService.SendCaptchaAsync(phoneNumber);
        viewModel.AuthPhase = AuthPhase.PhoneNumber;
        viewModel.PhoneNumber = phoneNumber;
        viewModel.IsAuthed = false;
        viewModel.Message = "短信验证码已发送, 请注意查看手机!";
        return viewModel.Desensitize();
    }
    /// <summary>
    /// 验证短信验证码
    /// </summary>
    /// <returns></returns>
    public async Task<RealNameViewModel> ValidatePhoneNumberAsync(string code)
    {
        var info = await _dataService.FirstOrDefaultAsync(x => x.Id == _dataService.UserId || x.UserId == _dataService.UserId);
        var viewModel = GetRealNameViewModel(info);
        var captcha = await _phoneNumberCaptchaService.ConfirmAsync(code);
        if (!string.IsNullOrWhiteSpace(captcha.ErrorMessage))
        {
            throw new BadHttpRequestException(captcha.ErrorMessage);
        }

        //验证通过时, 用户已登录进行实名认证的情况下:
        //1. 不存在 auth user, 则是系统用户, 先创建了系统账户在做实名认证的手机号码认证的情况, 此时正常创建 auth user, 并使用 user 已有信息填充 auth user, 更新 user 中的手机号码
        //2. 存在 auth user, 则是系统用户, 先创建了系统账户在已经完成手机号码认证, 进行重新认证的情况, 此时更新 auth user, 更新 user 中的手机号码
        var context = DataServiceContext.GetNoSaveChange();
        var user = await _userService.GetAsync(_userService.UserId);

        if (info is null)
        {
            //1. 如果不存在 auth user, 则是系统用户, 先创建了系统账户在做实名认证的手机号码认证的情况, 此时正常创建 auth user, 并使用 user 已有信息填充 auth user, 更新 user 中的手机号码
            info = new RealNameInfo
            {
                Id = _dataService.UserId,
                Name = captcha.PhoneNumber,
                Type = user?.Type ?? AuthorizeType.ThirdParty,
                Image = user?.Image,
                UserId = user?.Id,
                PhoneNumber = captcha.PhoneNumber,
                Captcha = code,
                SendTime = captcha.SendTime?.ToUniversalTime(),
                ValidateTime = DateTime.UtcNow,
                PhoneNumberValidateFailedCount = captcha.FailedCount,
                PhoneNumberValidateTime = DateTime.UtcNow,
                AuthPhase = AuthPhase.IdCard,
                IsAuthed = false
            };

            await _dataService.AddAsync(info, context);
        }
        else
        {
            //2. 如果已存在 auth user, 则是系统用户, 先创建了系统账户在已经完成手机号码认证, 进行重新认证的情况, 此时更新 auth user, 更新 user 中的手机号码
            info.UserId = user?.Id;
            info.PhoneNumber = captcha.PhoneNumber;
            info.Captcha = code;
            info.SendTime = captcha.SendTime?.ToUniversalTime();
            info.ValidateTime = DateTime.UtcNow;
            info.PhoneNumberValidateFailedCount = captcha.FailedCount;
            info.PhoneNumberValidateTime = DateTime.UtcNow;
            if (!info.IsAuthed)
            {
                info.AuthPhase = AuthPhase.IdCard;
            }

            await _dataService.UpdateAsync(info, context);
        }

        await _dataService.SaveChangesAsync();

        viewModel.AuthPhase = info.AuthPhase;
        viewModel.IsAuthed = info.IsAuthed;
        viewModel.Message = viewModel.IsAuthed ? "短信验证码正确, 手机号码验证已完成!" : "短信验证码正确, 请继续下一步验证!";
        return viewModel.Desensitize();
    }

    /// <summary>
    /// 身份证云接口在线识别验证方法
    /// </summary>
    /// <param name="attachments"></param>
    /// <returns></returns>
    public async Task<IdCardViewModel> GetIdCardInfosAsync(ICollection<Attachment> attachments)
    {
        var info = await _dataService.FirstOrDefaultAsync(x => x.Id == _dataService.UserId || x.UserId == _dataService.UserId);
        _logger.LogInformation($"用户 {_dataService.UserName} 将进行身份证认证识别!");
        if (info is null || !info.IsAuthed && info.AuthPhase < AuthPhase.IdCard)
        {
            throw new BadHttpRequestException("用户尚未进行手机号码认证, 请先进行手机号码认证!");
        }

        GetAvailable(info, AuthPhase.IdCard);
        var idCard = await _idCardService.GetIdCardInformationAsync(attachments);
        var state = _httpContext.GetFromHttpContext<DataState>(CLAIM_TYPE_USER_STATE);
        if (state != DataState.Draft)
        {
            info.UserId = _dataService.UserId;
        }

        info.IdCardValidateTime = DateTime.UtcNow;
        if (idCard is null)
        {
            info.IdCardValidateFailedCount++;
            await _dataService.UpdateAsync(info);
            throw new BadHttpRequestException("身份证验证失败, 请重新拍照提交!");
        }

        if (idCard.End.HasValue && idCard.End < DateTime.UtcNow)
        {
            info.IdCardValidateFailedCount++;
            await _dataService.UpdateAsync(info);
            throw new BadHttpRequestException("身份证已过期, 请提交有效身份证照片!");
        }

        await _dataService.UpdateAsync(info);
        _logger.LogInformation($"用户 {_dataService.UserName} 身份证 {idCard.IdCode} 已成功识别, 待用户确认!");
        return idCard;
    }
    /// <summary>
    /// 身份证验证通过方法, 验证通过后的身份证信息将关联认证用户信息
    /// </summary>
    /// <returns></returns>
    public async Task<RealNameViewModel> ValidateIdCardAsync(IdCardViewModel idCardViewModel)
    {
        var info = await _dataService.FirstOrDefaultAsync(x => x.Id == _dataService.UserId || x.UserId == _dataService.UserId);
        _logger.LogInformation($"用户 {_dataService.UserName} 将进行身份证认证!");
        if (info is null || !info.IsAuthed && info.AuthPhase < AuthPhase.IdCard)
        {
            throw new BadHttpRequestException("用户尚未进行手机号码认证, 请先进行手机号码认证!");
        }

        if (string.IsNullOrWhiteSpace(idCardViewModel.IdCode) || idCardViewModel.Attachments?.Count < 2)
        {
            throw new BadHttpRequestException("身份证认证阶段身份证号码不能为空, 且必须包含身份证正反面照片!");
        }

        var viewModel = GetAvailable(info, AuthPhase.IdCard);

        //验证通过时, 用户已登录进行实名认证的情况下:
        //更新 auth user 和 user 中的身份证信息
        var user = await _userService.GetAsync(_dataService.UserId);
        if (user is not null)
        {
            user.Name = idCardViewModel.Name;
            await _userService.UpdateAsync(user, DataServiceContext.GetNoSaveChange());
        }

        info.Name = idCardViewModel.Name;
        info.IdCode = idCardViewModel.IdCode;
        info.IdCardValidateTime = DateTime.UtcNow;
        info.AuthPhase = AuthPhase.IdCard + 1;
        await _idCardService.SaveAsync(idCardViewModel);
        await _dataService.UpdateAsync(info);

        viewModel.AuthPhase = info.AuthPhase;
        viewModel.IsAuthed = false;
        _logger.LogInformation($"用户 {_dataService.UserName} 身份证 {idCardViewModel.IdCode} 认证已完成!");
        return viewModel.Desensitize();
    }

    /// <summary>
    /// 身份证头像和人脸照片比对
    /// </summary>
    /// <param name="attachment"></param>
    /// <returns></returns>
    public async Task<RealNameViewModel> FaceCompareAsync(Attachment attachment)
    {
        var info = await _dataService.FirstOrDefaultAsync(x => x.Id == _dataService.UserId || x.UserId == _dataService.UserId);
        _logger.LogInformation($"用户 {_dataService.UserName} 将进行人脸比对!");
        if (info is null || string.IsNullOrWhiteSpace(info.IdCode) || !info.IsAuthed && info.AuthPhase < AuthPhase.FaceCompare)
        {
            throw new BadHttpRequestException("用户尚未进行身份证认证, 请先进行身份证认证!");
        }

        var viewModel = GetAvailable(info, AuthPhase.FaceCompare);
        var imagePath = attachment.Path;
        if (string.IsNullOrWhiteSpace(imagePath))
        {
            throw new BadHttpRequestException("未提交面部照片!");
        }

        imagePath = _fileService.GetAttachmentPath(imagePath);
        if (!File.Exists(imagePath))
        {
            throw new BadHttpRequestException("面部照片文件不存在!");
        }

        var idCard = await _idCardService.GetByIdCodeAsync(info.IdCode);
        if (idCard is null || (string.IsNullOrWhiteSpace(idCard.HeadImage) && string.IsNullOrWhiteSpace(idCard.FrontImage)))
        {
            throw new BadHttpRequestException("未完成身份证识别或未从身份证中识别出头像, 请重新进行身份证认证!");
        }

        //如果 HeadImage 不存在就先使用 FrontImage
        var headImage = _fileService.GetAttachmentPath(idCard.HeadImage ?? idCard.FrontImage ?? string.Empty);
        if (!File.Exists(headImage))
        {
            throw new BadHttpRequestException("身份证头像文件不存在!");
        }

        bool result;
        float score;

        try
        {
            (result, score) = await _faceRecognitionService.CompareAsync(headImage, imagePath);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"用户 {_dataService.UserName} 人脸比对失败, " + ex.GetMessage());
            throw new BadHttpRequestException("人脸比对失败, 请重试!");
        }

        info.FaceImage = attachment.Path;
        info.FaceMatchScore = (float)Math.Round(score, UNIT_FOUR);
        info.FaceCompareValidateTime = DateTime.UtcNow;
        _logger.LogInformation($"人脸活体检测结束, 比对得分: {info.FaceMatchScore}, 检测结果: {result}");

        if (!result)
        {
            info.FaceCompareValidateFailedCount++;
            await _dataService.UpdateAsync(info);
            throw new BadHttpRequestException("人脸比对认证未通过, 请重试!");
        }

        await _dataService.AddAttachmentsAsync(info, [attachment]);
        info.AuthPhase = AuthPhase.FaceCompare + 1;
        await _dataService.UpdateAsync(info);

        viewModel.AuthPhase = info.AuthPhase;
        viewModel.IsAuthed = info.IsAuthed;
        return viewModel.Desensitize();
    }
    /// <summary>
    /// 实名认证完成后提交人脸活体认证视频记录接口
    /// </summary>
    /// <param name="attachments"></param>
    /// <returns></returns>
    public async Task<RealNameViewModel> FaceDetectionAsync(ICollection<Attachment> attachments)
    {
        var info = await _dataService.FirstOrDefaultAsync(x => x.Id == _dataService.UserId || x.UserId == _dataService.UserId);
        _logger.LogInformation($"用户 {_dataService.UserName} 将进行人脸活体识别认证!");
        if (info is null || !info.IsAuthed && info.AuthPhase < AuthPhase.FaceDetection)
        {
            throw new BadHttpRequestException("用户尚未进行人脸比对认证, 请先进行人脸比对认证!");
        }

        var viewModel = GetAvailable(info, AuthPhase.FaceDetection);
        info.FaceDetectionValidateTime = DateTime.UtcNow;
        var video = attachments.FirstOrDefault(x => x.ContentType != null && x.ContentType.StartsWith("video"));
        var videoThumbnail = attachments.FirstOrDefault(x => x.ContentType != null && x.ContentType.StartsWith("image"));
        if (video is null)
        {
            info.FaceDetectionValidateFailedCount++;
            await _dataService.UpdateAsync(info);
            throw new BadHttpRequestException("实名认证视频或缩略图未提交!");
        }

        //info.IsAuthed = authUser.FaceMatchScore >= _appSetting.MatchFaceScore;
        info.IsAuthed = true;
        info.AuthVideo = video.Path;
        info.AuthVideoThumbnail = videoThumbnail?.Path;
        info.AuthPhase = AuthPhase.FaceDetection + 1;
        await _dataService.UpdateAsync(info);

        viewModel.AuthPhase = info.AuthPhase;
        viewModel.IsAuthed = info.IsAuthed;
        return viewModel;
    }

    /// <summary>
    /// 用于实名认证完成后, 在系统账户不存在的情况下, 使用实名认证信息创建用户信息
    /// </summary>
    /// <returns></returns>
    public async Task<User> GetOrCreateUserAsync(RealNameViewModel viewModel)
    {
        if (viewModel.AuthPhase != AuthPhase.Finish || string.IsNullOrWhiteSpace(viewModel.IdCode) || string.IsNullOrWhiteSpace(viewModel.PhoneNumber))
        {
            throw new Exception("用户尚未完成实名认证!");
        }

        var user = await _userService.FirstOrDefaultAsync(x => x.Name == viewModel.Name && x.PhoneNumber == viewModel.PhoneNumber);
        if ((user is null || !await _dataService.AnyAsync(x => x.AuthPhase == AuthPhase.Finish && x.UserId == user.Id && x.IdCode == viewModel.IdCode)))
        {
            var oauthUser = await _dataService.FirstOrDefaultAsync<OAuthUser>(x => x.OpenId == _dataService.UserId);
            user = new User
            {
                Name = viewModel.Name,
                Alias = oauthUser?.Alias ?? viewModel.Alias ?? viewModel.Name,
                Image = viewModel.Image,
                PhoneNumber = viewModel.PhoneNumber,
                PhoneNumberConfirmed = true,
                Type = AuthorizeType.ThirdParty,
                UserName = _dataService.UserId,
                NormalizedUserName = _dataService.UserId.ToUpperInvariant(),
                Description = oauthUser?.Description,
                LockoutEnabled = true
            };

            await _userService.AddAsync(user, DataServiceContext.GetNoSaveChange());
        }

        //关联用户
        var realNameInfo = await _dataService.GetIfExistAsync(viewModel.Id);
        realNameInfo.UserId = user.Id;
        await _dataService.UpdateAsync(realNameInfo);
        return user;
    }

    /// <summary>
    /// 获取当前用户对应的认证用户
    /// </summary>
    /// <returns></returns>
    private RealNameViewModel GetRealNameViewModel(RealNameInfo? info)
    {
        _logger.LogInformation("{user} 获取个人认证信息", _dataService.UserName);
        if (info is null)
        {
            //此时用户已登录, 但未进行实名认证, 因此不能使用登录用户编号查询到实名认证信息
            return new RealNameViewModel
            {
                Id = _dataService.UserId,
                Name = _dataService.UserName,
                UserId = _dataService.UserId
            };
        }

        return new RealNameViewModel
        {
            Id = info.Id,
            Name = info.Name,
            UserId = info.UserId,
            PhoneNumber = info.PhoneNumber,
            IdCode = info.IdCode,
            AuthPhase = info.AuthPhase,
            IsAuthed = info.IsAuthed
        };
    }
    /// <summary>
    /// 验证失败次数和失败尝试间隔时间是否超限, 未超限则返回认证对象视图模型
    /// </summary>
    /// <param name="info"></param>
    /// <returns></returns>
    private RealNameViewModel GetAvailable(RealNameInfo info, AuthPhase authPhase)
    {
        if (!info.IsAuthed && authPhase > info.AuthPhase)
        {
            throw new BadHttpRequestException($"用户 {info.Name} 尚未完成 {info.AuthPhase.GetDisplayName()}认证, 此时不能进行 {authPhase.GetDisplayName()} 认证!");
        }

        if ((authPhase == AuthPhase.PhoneNumber && info.PhoneNumberValidateFailedCount >= _httpSetting.Execute.MaxRetryCount && info.PhoneNumberValidateTime.HasValue && (DateTime.Today <= info.PhoneNumberValidateTime.Value.Date))
        || (authPhase == AuthPhase.IdCard && info.IdCardValidateFailedCount >= _httpSetting.Execute.MaxRetryCount && info.IdCardValidateTime.HasValue && (DateTime.Today <= info.IdCardValidateTime.Value.Date))
        || (authPhase == AuthPhase.FaceCompare && info.FaceCompareValidateFailedCount >= _httpSetting.Execute.MaxRetryCount && info.FaceCompareValidateTime.HasValue && (DateTime.Today <= info.FaceCompareValidateTime.Value.Date))
        || (authPhase == AuthPhase.FaceDetection && info.FaceDetectionValidateFailedCount >= _httpSetting.Execute.MaxRetryCount && info.FaceDetectionValidateTime.HasValue && (DateTime.Today <= info.FaceDetectionValidateTime.Value.Date)))
        {
            throw new BadHttpRequestException($"用户 {info.Name} 在 {authPhase.GetDisplayName()} 认证中连续失败次数已达上限 {_httpSetting.Execute.MaxRetryCount} 次, 且失败后间隔尝试的时间未超过允许的时间, 此时不能继续认证!");
        }

        if (authPhase == AuthPhase.PhoneNumber && info.PhoneNumberValidateFailedCount < _httpSetting.Execute.MaxRetryCount && info.PhoneNumberValidateTime.HasValue && (DateTime.Today > info.PhoneNumberValidateTime.Value.Date))
        {
            info.PhoneNumberValidateFailedCount = 0;
        }

        if (authPhase == AuthPhase.IdCard && info.IdCardValidateFailedCount < _httpSetting.Execute.MaxRetryCount && info.IdCardValidateTime.HasValue && (DateTime.Today > info.IdCardValidateTime.Value.Date))
        {
            info.IdCardValidateFailedCount = 0;
        }

        if (authPhase == AuthPhase.FaceCompare && info.FaceCompareValidateFailedCount < _httpSetting.Execute.MaxRetryCount && info.FaceCompareValidateTime.HasValue && (DateTime.Today > info.FaceCompareValidateTime.Value.Date))
        {
            info.FaceCompareValidateFailedCount = 0;
        }

        if (authPhase == AuthPhase.FaceDetection && info.FaceDetectionValidateFailedCount < _httpSetting.Execute.MaxRetryCount && info.FaceDetectionValidateTime.HasValue && (DateTime.Today > info.FaceDetectionValidateTime.Value.Date))
        {
            info.FaceDetectionValidateFailedCount = 0;
        }

        var viewModel = info.CopyTo<RealNameViewModel>();
        viewModel.AuthPhase = authPhase;
        return viewModel;
    }
}